package ar.edu.unq.leezsi.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import ar.edu.unq.leezsi.exception.CowebRuntimeException;

public class ReflectionUtils {

	public static <T> T invoke(Method method, Object target) {
		return invoke(method, target, new Object[0]);
	}

	public static <T> T invoke(Method method, Object target, Object... params) {
		try {
			boolean accessible = method.isAccessible();
			method.setAccessible(true);
			@SuppressWarnings("unchecked")
			T invoke = (T) method.invoke(target, params);
			method.setAccessible(accessible);
			return invoke;
		} catch (Exception e) {
			throw new CowebRuntimeException(e);
		}
	}

	public static ReflectionUtils target(final Class<?> bean) {
		return new ReflectionUtils(bean);
	}

	public static ReflectionUtils target(final Object bean) {
		return new ReflectionUtils(bean.getClass());
	}

	private final Class<? extends Object> targetClass;

	private ReflectionUtils(final Class<? extends Object> target) {
		this.targetClass = target;
	}

	public Field[] getAllFields() {
		final List<Field> allFields = this.getAllFields(new ArrayList<Field>());
		return allFields.toArray(new Field[allFields.size()]);
	}

	private List<Field> getAllFields(final List<Field> all) {
		if (this.getTarget() == null) {
			return all;
		} else {
			final Field[] declareds = this.getTarget().getDeclaredFields();
			all.addAll(Arrays.asList(declareds));
			final Class<?> superclass = this.getTarget().getSuperclass();
			return ReflectionUtils.target(superclass).getAllFields(all);
		}
	}

	public Method getGetter(String property) {
		try {
			return this.methodByName("get"
					+ Character.toUpperCase(property.charAt(0))
					+ property.substring(1));
		} catch (Exception e) {
			throw new CowebRuntimeException(e);
		}
	}

	public Method getSetter(String property) {
		try {
			return this.methodByName("set"
					+ Character.toUpperCase(property.charAt(0))
					+ property.substring(1));
		} catch (Exception e) {
			throw new CowebRuntimeException(e);
		}
	}

	public Class<? extends Object> getTarget() {
		return this.targetClass;
	}

	public Method methodByName(String methodName) {
		if (this.getTarget() == null) {
			throw new CowebRuntimeException("No such method " + methodName);
		}
		Method[] methods = this.getTarget().getDeclaredMethods();
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				return method;
			}
		}
		return ReflectionUtils.target(this.getTarget().getSuperclass())
				.methodByName(methodName);
	}

	public void set(final Object obj, final Field field, final Object bean) {
		try {
			final boolean isAccessible = field.isAccessible();
			field.setAccessible(true);
			field.set(obj, bean);
			field.setAccessible(isAccessible);
		} catch (final Exception e) {
			throw new CowebRuntimeException("Error setting field "
					+ field.getName(), e);
		}
	}

}
